package structs

import (
	"errors"
	"nprobe/utils/maths"
	"sync/atomic"
)

type PPRingBuffer[T any] struct {
	Cap   uint64
	PSize uint64

	mask uint64
	head uint64
	tail uint64

	data []T
}

func NewPPRingBuffer[e any](cap uint64, psize uint64) *PPRingBuffer[e] {
	normCap := maths.RoundUpPowerOfTwo(cap)
	mask := normCap - 1

	return &PPRingBuffer[e]{
		Cap:   normCap,
		PSize: psize,
		mask:  mask,
		head:  0,
		tail:  0,
		data:  make([]e, normCap),
	}
}

func (rb *PPRingBuffer[T]) PeekPong() (T, error) {
	size := (rb.Cap - rb.tail + atomic.LoadUint64(&rb.head)) & rb.mask
	if size <= rb.PSize {
		var zero T
		return zero, errors.New("empty")
	}

	return rb.data[rb.tail], nil
}

func (rb *PPRingBuffer[T]) Dequeue() (T, error) {
	size := (rb.Cap - rb.tail + atomic.LoadUint64(&rb.head)) & rb.mask
	if size <= rb.PSize {
		var zero T
		return zero, errors.New("empty")
	}

	v := rb.data[rb.tail]
	nextTail := (rb.tail + 1) & rb.mask
	atomic.StoreUint64(&rb.tail, nextTail)
	return v, nil
}

func (rb *PPRingBuffer[T]) DequeueAfterPeek() T {
	v := rb.data[rb.tail]
	nextTail := (rb.tail + 1) & rb.mask
	atomic.StoreUint64(&rb.tail, nextTail)
	return v
}

func (rb *PPRingBuffer[T]) Enqueue(v T) error {
	nextHead := (rb.head + 1) & rb.mask
	if nextHead == atomic.LoadUint64(&rb.tail) {
		return errors.New("full")
	}

	rb.data[rb.head] = v
	atomic.StoreUint64(&rb.head, nextHead)
	return nil
}

func (rb *PPRingBuffer[T]) PeekPing(offset uint64) (T, error) {
	if offset >= rb.PSize {
		var zero T
		return zero, errors.New("exceed ping boundary")
	}

	tail := atomic.LoadUint64(&rb.tail)
	count := (rb.Cap - tail + rb.head) & rb.mask
	if offset >= count {
		var zero T
		return zero, errors.New("exceed buffer element count")
	}
	idx := (rb.Cap - offset - 1 + rb.head) & rb.mask
	return rb.data[idx], nil
}

func (rb *PPRingBuffer[T]) UpdatePing(offset uint64, v T) error {
	if offset >= rb.PSize {
		return errors.New("exceed ping boundary")
	}

	tail := atomic.LoadUint64(&rb.tail)
	count := (rb.Cap - tail + rb.head) & rb.mask
	if offset >= count {
		return errors.New("exceed buffer element count")
	}

	idx := (rb.Cap - offset - 1 + rb.head) & rb.mask
	rb.data[idx] = v
	return nil
}

func (rb *PPRingBuffer[T]) IsEmpty() bool {
	return atomic.LoadUint64(&rb.head) == atomic.LoadUint64(&rb.tail)
}

func (rb *PPRingBuffer[T]) IsFull() bool {
	return (atomic.LoadUint64(&rb.head)+1)&rb.mask == atomic.LoadUint64(&rb.tail)
}

func (rb *PPRingBuffer[T]) Size() uint64 {
	return (rb.Cap - atomic.LoadUint64(&rb.tail) + atomic.LoadUint64(&rb.head)) & rb.mask
}
